/*
 * SoapUI, Copyright (C) 2004-2022 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.security.scan;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.MaliciousAttachmentConfig;
import com.eviware.soapui.config.MaliciousAttachmentElementConfig;
import com.eviware.soapui.config.MaliciousAttachmentSecurityScanConfig;
import com.eviware.soapui.config.SecurityScanConfig;
import com.eviware.soapui.config.StrategyTypeConfig;
import com.eviware.soapui.impl.support.AbstractHttpRequest;
import com.eviware.soapui.impl.wsdl.AttachmentContainer;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Attachment;
import com.eviware.soapui.model.iface.MessageExchange;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.security.SecurityTestRunContext;
import com.eviware.soapui.security.SecurityTestRunner;
import com.eviware.soapui.security.tools.RandomFile;
import com.eviware.soapui.security.ui.MaliciousAttachmentAdvancedSettingsPanel;
import com.eviware.soapui.security.ui.MaliciousAttachmentMutationsPanel;
import com.eviware.soapui.support.UISupport;

import javax.swing.JComponent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MaliciousAttachmentSecurityScan extends AbstractSecurityScan implements PropertyChangeListener {

    public static final String TYPE = "MaliciousAttachmentSecurityScan";
    public static final String NAME = "Malicious Attachment";

    private MaliciousAttachmentSecurityScanConfig config;

    private MaliciousAttachmentAdvancedSettingsPanel advancedSettingsPanel;
    private MaliciousAttachmentMutationsPanel mutationsPanel;

    private int elementIndex = -1;
    private int valueIndex = -1;

    private AbstractHttpRequest<?> request;

    public MaliciousAttachmentSecurityScan(TestStep testStep, SecurityScanConfig newConfig, ModelItem parent,
                                           String icon) {
        super(testStep, newConfig, parent, icon);

        if (newConfig.getConfig() == null || !(newConfig.getConfig() instanceof MaliciousAttachmentSecurityScanConfig)) {
            initConfig();
        } else {
            config = ((MaliciousAttachmentSecurityScanConfig) newConfig.getConfig());
        }

        request = ((AbstractHttpRequest<?>) getRequest(testStep));
        request.addAttachmentsChangeListener(this);
    }

    /**
     * Default malicious attachment configuration
     */
    protected void initConfig() {
        getConfig().setConfig(MaliciousAttachmentSecurityScanConfig.Factory.newInstance());
        config = (MaliciousAttachmentSecurityScanConfig) getConfig().getConfig();
    }

    private void generateFiles() {
        if (config != null) {
            for (MaliciousAttachmentElementConfig element : config.getElementList()) {
                for (MaliciousAttachmentConfig value : element.getGenerateAttachmentList()) {
                    File file = new File(value.getFilename());

                    try {
                        if (!file.exists() || file.length() == 0) {
                            file = new RandomFile(value.getSize(), value.getFilename(), value.getContentType()).next();
                        }
                    } catch (IOException e) {
                        SoapUI.logError(e);
                    }
                }
            }
        }
    }

    @Override
    public void updateSecurityConfig(SecurityScanConfig config) {
        super.updateSecurityConfig(config);

        if (this.config != null) {
            this.config = (MaliciousAttachmentSecurityScanConfig) getConfig().getConfig();
        }

        if (advancedSettingsPanel != null) {
            advancedSettingsPanel.setConfig((MaliciousAttachmentSecurityScanConfig) getConfig().getConfig());
        }

        if (mutationsPanel != null) {
            mutationsPanel.updateConfig((MaliciousAttachmentSecurityScanConfig) getConfig().getConfig());
        }
    }

    public MaliciousAttachmentSecurityScanConfig getMaliciousAttachmentSecurityScanConfig() {
        return config;
    }

    /*
     * Set attachments. Strategy determines the number of existing attachments
     * used (one/all)
     */
    private void updateRequestContent(TestStep testStep, SecurityTestRunContext context) {
        if (config.getRequestTimeout() > 0) {
            setRequestTimeout(testStep, config.getRequestTimeout());
        }

        if (getExecutionStrategy().getStrategy() == StrategyTypeConfig.ONE_BY_ONE) {
            if (elementIndex == -1) {
                elementIndex++;
            }

            if (elementIndex < config.getElementList().size()) {
                MaliciousAttachmentElementConfig element = config.getElementList().get(elementIndex);
                removeAttachments(testStep, element.getKey(), false);
                if (element.getRemove()) {
                    removeAttachments(testStep, element.getKey(), true);
                }

                if (valueIndex < element.getGenerateAttachmentList().size() + element.getReplaceAttachmentList().size() - 1) {
                    valueIndex++;
                    addAttachments(testStep, element, valueIndex);
                }

                if (valueIndex == element.getGenerateAttachmentList().size() + element.getReplaceAttachmentList().size()
                        - 1) {
                    valueIndex = -1;
                    elementIndex++;
                }
            }
        } else if (getExecutionStrategy().getStrategy() == StrategyTypeConfig.ALL_AT_ONCE) {
            if (elementIndex == -1) {
                elementIndex++;
            }

            executeAllAtOnce(testStep);
            valueIndex++;
        }
    }

    private void executeAllAtOnce(TestStep testStep) {
        for (MaliciousAttachmentElementConfig element : config.getElementList()) {
            if (element.getRemove()) {
                removeAttachments(testStep, element.getKey(), true);
            }

            int valIndex = valueIndex;

            if (valIndex < element.getGenerateAttachmentList().size() + element.getReplaceAttachmentList().size() - 1) {
                valIndex++;
                addAttachments(testStep, element, valIndex);
            }

            if (valIndex + 1 > element.getGenerateAttachmentList().size() + element.getReplaceAttachmentList().size() - 1) {
                elementIndex++;
            }
        }
    }

    private void addAttachments(TestStep testStep, MaliciousAttachmentElementConfig element, int counter) {
        if (counter == -1) {
            return;
        }

        boolean generated = false;
        List<MaliciousAttachmentConfig> list = null;

        if (counter < element.getGenerateAttachmentList().size()) {
            generated = true;
            list = element.getGenerateAttachmentList();
        } else {
            list = element.getReplaceAttachmentList();
            counter = counter - element.getGenerateAttachmentList().size();
        }

        MaliciousAttachmentConfig value = list.get(counter);
        File file = new File(value.getFilename());

        if (value.getEnabled()) {
            try {
                if (!file.exists()) {
                    UISupport.showErrorMessage("Missing file: " + file.getName());
                    return;
                }

                addAttachment(testStep, file, value.getContentType(), generated, value.getCached());
            } catch (IOException e) {
                SoapUI.logError(e);
            }
        }
    }

    @Override
    protected void execute(SecurityTestRunner securityTestRunner, TestStep testStep, SecurityTestRunContext context) {
        try {
            request.removeAttachmentsChangeListener(this);
            generateFiles();
            updateRequestContent(testStep, context);
            MessageExchange message = (MessageExchange) testStep.run((TestCaseRunner) securityTestRunner, context);
            getSecurityScanRequestResult().setMessageExchange(message);
            request.addAttachmentsChangeListener(this);
        } catch (Exception e) {
            SoapUI.logError(e, "[MaliciousAttachmentSecurityScan]Property value is not valid xml!");
            reportSecurityScanException("Property value is not XML or XPath is wrong!");
        }
    }

    @Override
    public String getType() {
        return TYPE;
    }

    private Attachment addAttachment(TestStep testStep, File file, String contentType, boolean generated, boolean cache)
            throws IOException {
        AbstractHttpRequest<?> request = (AbstractHttpRequest<?>) getRequest(testStep);
        Attachment attach = request.attachFile(file, cache);
        attach.setContentType(contentType);

        return attach;
    }

    private void removeAttachments(TestStep testStep, String key, boolean equals) {
        List<Attachment> toRemove = new ArrayList<>();
        AbstractHttpRequest<?> request = (AbstractHttpRequest<?>) getRequest(testStep);

        for (Attachment attachment : request.getAttachments()) {
            if (equals) {
                if (attachment.getId().equals(key)) {
                    toRemove.add(attachment);
                }
            } else {
                if (!attachment.getId().equals(key)) {
                    toRemove.add(attachment);
                }
            }
        }
        for (Attachment remove : toRemove) {
            request.removeAttachment(remove);
        }

    }

    private void setRequestTimeout(TestStep testStep, int timeout) {
        AbstractHttpRequest<?> request = (AbstractHttpRequest<?>) getRequest(testStep);
        request.setTimeout(String.valueOf(timeout));

    }

    @Override
    public JComponent getComponent() {
        if (mutationsPanel == null) {
            mutationsPanel = new MaliciousAttachmentMutationsPanel(config,
                    (AbstractHttpRequest<?>) getRequest(getTestStep()));
        }

        return mutationsPanel.getPanel();
    }

    @Override
    protected boolean hasNext(TestStep testStep, SecurityTestRunContext context) {
        AbstractHttpRequest<?> request = (AbstractHttpRequest<?>) getRequest(testStep);
        boolean hasNext = request.getAttachmentCount() == 0 ? false : elementIndex < config.getElementList().size();

        if (!hasNext) {
            elementIndex = -1;
            valueIndex = -1;
        }

        return hasNext;
    }

    @Override
    public String getConfigDescription() {
        return "Configures malicious attachment security scan";
    }

    @Override
    public String getConfigName() {
        return "Malicious Attachment Security Scan";
    }

    @Override
    public String getHelpURL() {
        return "http://soapui.org/Security/malicious-attachment.html";
    }

    @Override
    public JComponent getAdvancedSettingsPanel() {
        if (advancedSettingsPanel == null) {
            advancedSettingsPanel = new MaliciousAttachmentAdvancedSettingsPanel(config);
        }

        return advancedSettingsPanel.getPanel();
    }

    @Override
    public void copyConfig(SecurityScanConfig config) {
        super.copyConfig(config);

        if (advancedSettingsPanel != null) {
            advancedSettingsPanel.setConfig((MaliciousAttachmentSecurityScanConfig) getConfig().getConfig());
        }

        if (mutationsPanel != null) {
            mutationsPanel.updateConfig((MaliciousAttachmentSecurityScanConfig) getConfig().getConfig());
        }
    }

    @Override
    public void release() {
        if (advancedSettingsPanel != null) {
            advancedSettingsPanel.release();
        }

        if (mutationsPanel != null) {
            mutationsPanel.release();
        }

        if (request != null) {
            request.removeAttachmentsChangeListener(this);
        }

        super.release();
    }

    private void addedAttachment(Attachment attachment) {
        if (config != null) {
            MaliciousAttachmentElementConfig element = config.addNewElement();
            element.setKey(attachment.getId());
        }
    }

    private void removedAttachment(Attachment attachment) {
        if (config != null) {
            int idx = -1;

            for (int i = 0; i < config.getElementList().size(); i++) {
                MaliciousAttachmentElementConfig element = config.getElementList().get(i);
                if (attachment.getId().equals(element.getKey())) {
                    idx = i;
                    break;
                }
            }

            if (idx != -1) {
                config.removeElement(idx);
            }
        }

        if (mutationsPanel != null) {
            mutationsPanel.getHolder().removeAttachment(attachment.getId());
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (AttachmentContainer.ATTACHMENTS_PROPERTY.equals(evt.getPropertyName())) {
            if (evt.getOldValue() == null && evt.getNewValue() != null) {
                if (evt.getNewValue() instanceof Attachment) {
                    Attachment attachment = (Attachment) evt.getNewValue();
                    addedAttachment(attachment);
                }
            } else if (evt.getOldValue() != null && evt.getNewValue() == null) {
                if (evt.getOldValue() instanceof Attachment) {
                    Attachment attachment = (Attachment) evt.getOldValue();
                    removedAttachment(attachment);
                }
            }
        }
    }
}
